
#pragma once

#include "config.hpp"

#include "atr/algorithm.hpp"
#include "atr/functional.hpp"
#include "atr/core.hpp"

using namespace atr;
using namespace atr::core;

template <typename T>
class OperationsTest : public ::testing::Test
{};

TYPED_TEST_CASE_P(OperationsTest);

TYPED_TEST_P(OperationsTest, Remove)
{
    using T1 = TYPE_PARAM<int, long, int, int, char>::template remove<int>;
    using T2 = TYPE_PARAM<int, long, int, int, char>::template remove<long>;
    using T3 = TYPE_PARAM<int, long, int, int, char>::template remove<char>;

    using E1 = TYPE_PARAM<long, char>;
    using E2 = TYPE_PARAM<int, int, int, char>;
    using E3 = TYPE_PARAM<int, long, int, int>;

    EXPECT_SAME( E1, T1 );
    EXPECT_SAME( E2, T2 );
    EXPECT_SAME( E3, T3 );
}

TYPED_TEST_P(OperationsTest, Remove_EmptyList)
{
    using T = TYPE_PARAM<>::template remove<int>;
    using E = TYPE_PARAM<>;

    EXPECT_SAME ( E, T );
}

template <class T>
using predicate = typename atr::_or<typename atr::equal<T, int>::type,
                                    typename atr::equal<T, char>::type>::type;
TYPED_TEST_P(OperationsTest, RemoveIf)
{

    EXPECT_SAME( _true, predicate<int> );
    EXPECT_SAME( _true, predicate<char> );

    EXPECT_SAME( _false, predicate<size_t> );
    EXPECT_SAME( _false, predicate<long> );
    EXPECT_SAME( _false, predicate<unsigned> );

    using T1 = TYPE_PARAM<int, size_t, char, long, unsigned>
                                            ::template remove_if<predicate>;
    using T2 = TYPE_PARAM<int, int, int, long, unsigned>
                                            ::template remove_if<predicate>;
    using T3 = TYPE_PARAM<int, int, int, char, char, unsigned>
                                            ::template remove_if<predicate>;

    using E1 = TYPE_PARAM<size_t, long, unsigned>;
    using E2 = TYPE_PARAM<long, unsigned>;
    using E3 = TYPE_PARAM<unsigned>;

    EXPECT_SAME( E1, T1 );
    EXPECT_SAME( E2, T2 );
    EXPECT_SAME( E3, T3 );
}

TYPED_TEST_P(OperationsTest, RemoveIf_EmptyList)
{
    using T = TYPE_PARAM<>::template remove_if<predicate>;

    EXPECT_SAME (TYPE_PARAM<>, T );
}

TYPED_TEST_P(OperationsTest, Reverse_EmptyList)
{
    using T = TYPE_PARAM<>::reverse;
    using E = TYPE_PARAM<>;

    EXPECT_SAME( E, T );
}

TYPED_TEST_P(OperationsTest, Reverse_OneType)
{
    using T = TYPE_PARAM<int>::reverse;
    using E = TYPE_PARAM<int>;

    EXPECT_SAME( E, T );
}

TYPED_TEST_P(OperationsTest, Reverse_NonEmptyList)
{
    using T = TYPE_PARAM<int, long, char, unsigned>::reverse;
    using E = TYPE_PARAM<unsigned, char, long, int>;

    EXPECT_SAME( E, T );
}

TYPED_TEST_P(OperationsTest, Unique_EmptyList)
{
        using T = TYPE_PARAM<>::template unique<>;
        using E = TYPE_PARAM<>;

        EXPECT_SAME( E, T );
}

TYPED_TEST_P(OperationsTest, Unique_NonEmptyList)
{
    using T1 = TYPE_PARAM<int>::template unique<>;
    using T2 = TYPE_PARAM<int, char>::template unique<>;
    using T3 = TYPE_PARAM<int, int>::template unique<>;

    using T4 = TYPE_PARAM<int, char, int>::template unique<>;
    using T5 = TYPE_PARAM<int, char, char>::template unique<>;
    using T6 = TYPE_PARAM<int, int, char>::template unique<>;

    using T7 = TYPE_PARAM<int, char, char, long, size_t, long, int, int>
                                                ::template unique<>;
    using T8 = TYPE_PARAM<int, int, char, long, size_t, long, int, int>
                                                ::template unique<>;
    using T9 = TYPE_PARAM<int, int, int, int, size_t, long, int, int>
                                                ::template unique<>;
    using T0 = TYPE_PARAM<int, char, int, int, int, long, int, int>
                                                ::template unique<>;

    using E1 = TYPE_PARAM<int>;
    using E2 = TYPE_PARAM<int, char>;
    using E3 = TYPE_PARAM<int>;

    using E4 = TYPE_PARAM<int, char, int>;
    using E5 = TYPE_PARAM<int, char>;
    using E6 = TYPE_PARAM<int, char>;

    using E7 = TYPE_PARAM<int, char, long, size_t, long, int>;
    using E8 = TYPE_PARAM<int, char, long, size_t, long, int>;
    using E9 = TYPE_PARAM<int, size_t, long, int>;
    using E0 = TYPE_PARAM<int, char, int, long, int>;

    EXPECT_SAME( E1, T1 );
    EXPECT_SAME( E2, T2 );
    EXPECT_SAME( E3, T3 );

    EXPECT_SAME( E4, T4 );
    EXPECT_SAME( E5, T5 );
    EXPECT_SAME( E6, T6 );

    EXPECT_SAME( E7, T7 );
    EXPECT_SAME( E8, T8 );
    EXPECT_SAME( E9, T9 );
    EXPECT_SAME( E0, T0 );
}

template <class T1, class T2>
using predicate2 = typename atr::_and<typename atr::equal<T1, int>::type,
                                      typename atr::equal<T2, char>::type>;
TYPED_TEST_P(OperationsTest, Unique_EmptyList_Predicate)
{
    using T = TYPE_PARAM<>::template unique<predicate2>;

    using E = TYPE_PARAM<>;

    EXPECT_SAME ( E, T );
}

TYPED_TEST_P(OperationsTest, Unique_NonEmptyList_Predicate)
{
    using T1 = TYPE_PARAM<int>::template unique<predicate2>;
    using T2 = TYPE_PARAM<int, char>::template unique<predicate2>;
    using T3 = TYPE_PARAM<int, int>::template unique<predicate2>;

    using T4 = TYPE_PARAM<int, char, int>
                                    ::template unique<predicate2>;
    using T5 = TYPE_PARAM<int, char, char>
                                    ::template unique<predicate2>;
    using T6 = TYPE_PARAM<int, int, char>
                                    ::template unique<predicate2>;

    using T7 = TYPE_PARAM<int, char, char, long, size_t, long, int, int>
                                    ::template unique<predicate2>;
    using T8 = TYPE_PARAM<int, int, char, long, size_t, long, int, int>
                                    ::template unique<predicate2>;
    using T9 = TYPE_PARAM<int, int, int, int, size_t, long, int, int>
                                    ::template unique<predicate2>;
    using T0 = TYPE_PARAM<int, char, int, int, int, long, int, int>
                                    ::template unique<predicate2>;

    using E1 = TYPE_PARAM<int>;
    using E2 = TYPE_PARAM<int>;
    using E3 = TYPE_PARAM<int, int>;

    using E4 = TYPE_PARAM<int, int>;
    using E5 = TYPE_PARAM<int>;
    using E6 = TYPE_PARAM<int, int>;

    using E7 = TYPE_PARAM<int, long, size_t, long, int, int>;
    using E8 = TYPE_PARAM<int, int, long, size_t, long, int, int>;
    using E9 = TYPE_PARAM<int, int, int, int, size_t, long, int, int>;
    using E0 = TYPE_PARAM<int, int, int, int, long, int, int>;

    EXPECT_SAME( E1, T1 );
    EXPECT_SAME( E2, T2 );
    EXPECT_SAME( E3, T3 );

    EXPECT_SAME( E4, T4 );
    EXPECT_SAME( E5, T5 );
    EXPECT_SAME( E6, T6 );

    EXPECT_SAME( E7, T7 );
    EXPECT_SAME( E8, T8 );
    EXPECT_SAME( E9, T9 );
    EXPECT_SAME( E0, T0 );
}

TYPED_TEST_P(OperationsTest, Merge_WithEmptyList)
{
    using T = TYPE_PARAM<>;

    using T0 = TYPE_PARAM<>::template merge<T>;
    using T1 = TYPE_PARAM<int>::template merge<T>;
    using T2 = TYPE_PARAM<long>::template merge<T>;

    using E0 = TYPE_PARAM<>;
    using E1 = TYPE_PARAM<int>;
    using E2 = TYPE_PARAM<long>;

    EXPECT_SAME( E0, T0 );
    EXPECT_SAME( E1, T1 );
    EXPECT_SAME( E2, T2 );
}

TYPED_TEST_P(OperationsTest, Merge_EmptyList)
{
    using T_0 = TYPE_PARAM<>;
    using T_1 = TYPE_PARAM<int>;
    using T_2 = TYPE_PARAM<long>;

    using T0 = TYPE_PARAM<>::template merge<T_0>;
    using T1 = TYPE_PARAM<>::template merge<T_1>;
    using T2 = TYPE_PARAM<>::template merge<T_2>;

    using E0 = TYPE_PARAM<>;
    using E1 = TYPE_PARAM<int>;
    using E2 = TYPE_PARAM<long>;

    EXPECT_SAME( E0, T0 );
    EXPECT_SAME( E1, T1 );
    EXPECT_SAME( E2, T2 );
}

TYPED_TEST_P(OperationsTest, Merge_SimpleCase)
{
    using T_0 = TYPE_PARAM<int, int>;
    using T_1 = TYPE_PARAM<int, int>;
    using T_2 = TYPE_PARAM<long[2]>;

    using T1 = TYPE_PARAM<int, int>::template merge<T_1>;
    using T2 = TYPE_PARAM<int, int>::template merge<T_2>;
    using T3 = TYPE_PARAM<long>::template merge<T_0>;
    using T4 = TYPE_PARAM<int, char>::template merge<T_2>;
    using T5 = TYPE_PARAM<int, long[2], char>::template merge<T_2>;
    using T6 = TYPE_PARAM<char, long>::template merge<T_0>;

    using E1 = TYPE_PARAM<int, int, int, int>;
    using E2 = TYPE_PARAM<int, int, long[2]>;
    using E3 = TYPE_PARAM<int, int, long>;
    using E4 = TYPE_PARAM<int, char, long[2]>;
    using E5 = TYPE_PARAM<int, long[2], long[2], char>;
    using E6 = TYPE_PARAM<char, int, int, long>;

    EXPECT_SAME( E1, T1 );
    EXPECT_SAME( E2, T2 );
    EXPECT_SAME( E3, T3 );
    EXPECT_SAME( E4, T4 );
    EXPECT_SAME( E5, T5 );
    EXPECT_SAME( E6, T6 );
}

TYPED_TEST_P(OperationsTest, Merge_ComplexCase)
{
    // TODO
}

template <class T1, class T2>
using Comparator = typename core::_not<typename less<T1, T2>::type>;

TYPED_TEST_P(OperationsTest, Merge_WithEmptyList_CustomComparator)
{
    using T = TYPE_PARAM<>;

    using T0 = TYPE_PARAM<>::template merge<T, Comparator>;
    using T1 = TYPE_PARAM<int>::template merge<T, Comparator>;
    using T2 = TYPE_PARAM<long>::template merge<T, Comparator>;

    using E0 = TYPE_PARAM<>;
    using E1 = TYPE_PARAM<int>;
    using E2 = TYPE_PARAM<long>;

    EXPECT_SAME( E0, T0 );
    EXPECT_SAME( E1, T1 );
    EXPECT_SAME( E2, T2 );
}

TYPED_TEST_P(OperationsTest, Merge_EmptyList_CustomComparator)
{
    using T_0 = TYPE_PARAM<>;
    using T_1 = TYPE_PARAM<int>;
    using T_2 = TYPE_PARAM<long>;

    using T0 = TYPE_PARAM<>::template merge<T_0, Comparator>;
    using T1 = TYPE_PARAM<>::template merge<T_1, Comparator>;
    using T2 = TYPE_PARAM<>::template merge<T_2, Comparator>;

    using E0 = TYPE_PARAM<>;
    using E1 = TYPE_PARAM<int>;
    using E2 = TYPE_PARAM<long>;

    EXPECT_SAME( E0, T0 );
    EXPECT_SAME( E1, T1 );
    EXPECT_SAME( E2, T2 );
}

TYPED_TEST_P(OperationsTest, Merge_SimpleCase_CustomComparator)
{
    using T_0 = TYPE_PARAM<int, int>;
    using T_1 = TYPE_PARAM<int, int>;
    using T_2 = TYPE_PARAM<long[2]>;

    using T1 = TYPE_PARAM<int, int>::template merge<T_1, Comparator>;
    using T2 = TYPE_PARAM<int, int>::template merge<T_2, Comparator>;
    using T3 = TYPE_PARAM<long>::template merge<T_0, Comparator>;
    using T4 = TYPE_PARAM<int, char>::template merge<T_2, Comparator>;
    using T5 = TYPE_PARAM<int, long[2], char>::template merge<T_2, Comparator>;
    using T6 = TYPE_PARAM<char, long>::template merge<T_0, Comparator>;

    using E1 = TYPE_PARAM<int, int, int, int>;
    using E2 = TYPE_PARAM<long[2], int, int>;
    using E3 = TYPE_PARAM<long, int, int>;
    using E4 = TYPE_PARAM<long[2], int, char>;
    using E5 = TYPE_PARAM<long[2], int, long[2], char>;
    using E6 = TYPE_PARAM<int, int, char, long>;

    EXPECT_SAME( E1, T1 );
    EXPECT_SAME( E2, T2 );
    EXPECT_SAME( E3, T3 );
    EXPECT_SAME( E4, T4 );
    EXPECT_SAME( E5, T5 );
    EXPECT_SAME( E6, T6 );
}

TYPED_TEST_P(OperationsTest, Merge_ComplexCase_CustomComparator)
{
    // TODO
}

TYPED_TEST_P(OperationsTest, Splice_EntireList)
{
    using T_0 = TYPE_PARAM<int, long, char>;
    using T_1 = TYPE_PARAM<int, char>;

    using H_0 = TYPE_PARAM<int, char, long>;
    using H_1 = TYPE_PARAM<size_t, unsigned>;

    using T0 = typename T_0::template splice<typename T_0::begin, H_0>;
    using T1 = typename T_1::template splice<typename T_1::begin::next, H_1>;
    using T2 = typename T_1::template splice<typename T_1::end, H_1>;

    using E0 = TYPE_PARAM<int, char, long, int, long, char>;
    using E1 = TYPE_PARAM<int, size_t, unsigned, char>;
    using E2 = TYPE_PARAM<int, char, size_t, unsigned>;

    EXPECT_SAME( E0, T0 );
    EXPECT_SAME( E1, T1 );
    EXPECT_SAME( E2, T2 );
}

TYPED_TEST_P(OperationsTest, Splice_Element)
{
    using T_0 = TYPE_PARAM<int, long, char>;
    using T_1 = TYPE_PARAM<int, char>;

    using H_0 = TYPE_PARAM<int, char, long>;
    using H_1 = TYPE_PARAM<size_t, unsigned>;

    using T0 = typename T_0::template splice<typename T_0::begin,
                                             H_0, typename H_0::begin>;
    using T1 = typename T_1::template splice<typename T_1::begin::next,
                                             H_1, typename H_1::begin::next>;
    using T2 = typename T_1::template splice<typename T_1::end,
                                             H_1, typename H_1::begin>;

    using E0 = TYPE_PARAM<int, int, long, char>;
    using E1 = TYPE_PARAM<int, unsigned, char>;
    using E2 = TYPE_PARAM<int, char, size_t>;

    EXPECT_SAME( E0, T0 );
    EXPECT_SAME( E1, T1 );
    EXPECT_SAME( E2, T2 );
}

TYPED_TEST_P(OperationsTest, Splice_Range)
{
    using T_0 = TYPE_PARAM<int, long, char>;
    using T_1 = TYPE_PARAM<int, char>;

    using H_0 = TYPE_PARAM<int, char, long>;

    using T0 = typename T_0::template splice<typename T_0::begin, H_0,
                                             typename H_0::begin,
                                             typename H_0::end::prev>;
    using T1 = typename T_1::template splice<typename T_1::begin::next, H_0,
                                             typename H_0::begin::next,
                                             typename H_0::end>;
    using T2 = typename T_1::template splice<typename T_1::end, H_0,
                                             typename H_0::begin::next,
                                             typename H_0::end>;

    using E0 = TYPE_PARAM<int, char, int, long, char>;
    using E1 = TYPE_PARAM<int, char, long, char>;
    using E2 = TYPE_PARAM<int, char, char, long>;

    EXPECT_SAME( E0, T0 );
    EXPECT_SAME( E1, T1 );
    EXPECT_SAME( E2, T2 );
}

TYPED_TEST_P(OperationsTest, Sort)
{
    using T0 = TYPE_PARAM<>::template sort<>;
    using T1 = TYPE_PARAM<int>::template sort<>;
    using T2 = TYPE_PARAM<int, int>::template sort<>;
    using T3 = TYPE_PARAM<int, char, long[2]>::template sort<>;
    using T4 = TYPE_PARAM<long[2], char, int>::template sort<>;
    using T5 = TYPE_PARAM<char, int, long[2], char>::template sort<>;

    using E0 = TYPE_PARAM<>;
    using E1 = TYPE_PARAM<int>;
    using E2 = TYPE_PARAM<int, int>;
    using E3 = TYPE_PARAM<char, int, long[2]>;
    using E4 = TYPE_PARAM<char, int ,long[2]>;
    using E5 = TYPE_PARAM<char, char, int, long[2]>;

    EXPECT_SAME( E0, T0 );
    EXPECT_SAME( E1, T1 );
    EXPECT_SAME( E2, T2 );
    EXPECT_SAME( E3, T3 );
    EXPECT_SAME( E4, T4 );
    EXPECT_SAME( E5, T5 );
}

TYPED_TEST_P(OperationsTest, Sort_CustomComparator)
{
    using T0 = TYPE_PARAM<>::template sort<Comparator>;
    using T1 = TYPE_PARAM<int>::template sort<Comparator>;
    using T2 = TYPE_PARAM<int, int>::template sort<Comparator>;
    using T3 = TYPE_PARAM<int, char, long[2]>::template sort<Comparator>;
    using T4 = TYPE_PARAM<long[2], char, int>::template sort<Comparator>;
    using T5 = TYPE_PARAM<char, int, long[2], char>::template sort<Comparator>;

    using E0 = TYPE_PARAM<>;
    using E1 = TYPE_PARAM<int>;
    using E2 = TYPE_PARAM<int, int>;
    using E3 = TYPE_PARAM<long[2], int, char>;
    using E4 = TYPE_PARAM<long[2], int, char>;
    using E5 = TYPE_PARAM<long[2], int, char, char>;

    EXPECT_SAME( E0, T0 );
    EXPECT_SAME( E1, T1 );
    EXPECT_SAME( E2, T2 );
    EXPECT_SAME( E3, T3 );
    EXPECT_SAME( E4, T4 );
    EXPECT_SAME( E5, T5 );
}

TYPED_TEST_P(OperationsTest, Sort_One)
{
    using T0 = TYPE_PARAM<int>::template sort<>;
    using T1 = TYPE_PARAM<int>::template sort<Comparator>;

    using E0 = TYPE_PARAM<int>;
    using E1 = TYPE_PARAM<int>;

    EXPECT_SAME( E0, T0 );
    EXPECT_SAME( E1, T1 );
}

REGISTER_TYPED_TEST_CASE_P(OperationsTest, Remove,
                                           Remove_EmptyList,
                                           RemoveIf,
                                           RemoveIf_EmptyList,
                                           Reverse_EmptyList,
                                           Reverse_OneType,
                                           Reverse_NonEmptyList,
                                           Unique_EmptyList,
                                           Unique_NonEmptyList,
                                           Unique_EmptyList_Predicate,
                                           Unique_NonEmptyList_Predicate,
                                           Merge_WithEmptyList,
                                           Merge_EmptyList,
                                           Merge_SimpleCase,
                                           Merge_ComplexCase,
                                           Merge_WithEmptyList_CustomComparator,
                                           Merge_EmptyList_CustomComparator,
                                           Merge_SimpleCase_CustomComparator,
                                           Merge_ComplexCase_CustomComparator,
                                           Splice_EntireList,
                                           Splice_Element,
                                           Splice_Range,
                                           Sort,
                                           Sort_CustomComparator,
                                           Sort_One);
